package com.lmh.friend.service.user.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.lmh.common.core.constants.CacheConstants;
import com.lmh.common.core.constants.Constants;
import com.lmh.common.core.constants.HttpConstants;
import com.lmh.common.core.domain.LoginUser;
import com.lmh.common.core.domain.R;
import com.lmh.common.core.domain.vo.LoginUserVO;
import com.lmh.common.core.enums.ResultCode;
import com.lmh.common.core.enums.UserIdentity;
import com.lmh.common.core.enums.UserStatus;
import com.lmh.common.core.utils.ThreadLocalUtil;
import com.lmh.common.message.service.AliSmsService;
import com.lmh.common.redis.service.RedisService;
import com.lmh.common.security.exception.ServiceException;
import com.lmh.common.security.service.TokenService;
import com.lmh.friend.domain.user.User;
import com.lmh.friend.domain.user.dto.UserDTO;
import com.lmh.friend.domain.user.dto.UserUpdateDTO;
import com.lmh.friend.domain.user.vo.UserVO;
import com.lmh.friend.manager.UserCacheManager;
import com.lmh.friend.mapper.user.UserMapper;
import com.lmh.friend.service.user.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@RefreshScope
@Service
public class UserServiceImpl implements IUserService {
    @Autowired
    private UserMapper userMapper;

    @Autowired
    private RedisService redisService;

    @Autowired
    private AliSmsService aliSmsService;

    @Value("${sms.code-expiration:5}")
    private Long phoneCodeExpiration;

    @Value("${sms.send-limit:3}")
    private Integer sendLimit;

    @Autowired
    private TokenService tokenService;

    @Value("${jwt.secret}")
    private String secret;

    @Value("${sms.isSend}")
    private boolean isSend;

    @Autowired
    private UserCacheManager userCacheManager;

    @Value("${file.oss.downloadUrl}")
    private String downloadUrl;

    @Override
    public boolean sendCode(UserDTO userDTO) {
        // 检验前端传来的电话号码是否是正确的
        if (!checkPhone(userDTO.getPhone())) {
            throw new ServiceException(ResultCode.FAILED_USER_PHONE);
        }
        String phoneCodeKey = getPhoneCodeKey(userDTO.getPhone());
        // 计算上次获取验证码的时候是否过去了一分钟
        Long expire = redisService.getExpire(phoneCodeKey, TimeUnit.SECONDS);
        if (expire != null && (phoneCodeExpiration * 60 - expire) < 60) {
            throw new ServiceException(ResultCode.FAILED_FREQUENT);
        }
        // 检查今天获取验证码的次数是否超过了 timeLimit
        String codeTimeKey = getCodeTimeKey(userDTO.getPhone());
        Long sendTimes = redisService.getCacheObject(codeTimeKey, Long.class);
        if (sendTimes != null && sendTimes >= sendLimit) {
            throw new ServiceException(ResultCode.FAILED_TIME_LIMIT);
        }
        // 生成验证码
        String code = isSend ? RandomUtil.randomNumbers(6) : "123456";
        // 发送验证码
        if (isSend) {
            boolean sendMobileCode  = aliSmsService.sendMobileCode(userDTO.getPhone(), code);
            if (!sendMobileCode) {
                throw new ServiceException(ResultCode.FAILED_SEND_CODE);
            }
        }
        // 将验证码存储在redis中
        redisService.setCacheObject(phoneCodeKey,code,phoneCodeExpiration,TimeUnit.MINUTES);
        // 将获取验证码的次数加1
        redisService.increment(codeTimeKey);
        // 设置记录该用户一天获取验证码的次数的过期时间为今天一天
        if (sendTimes == null) {
            long seconds = ChronoUnit.SECONDS.between(LocalDateTime.now(),
                    LocalDateTime.now().plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0));
            redisService.expire(codeTimeKey,seconds,TimeUnit.SECONDS);
        }
        return true;
     }

    @Override
    public String codeLogin(UserDTO userDTO) {
        // 对前端传递来的参数进行检验
       checkParams(userDTO);
        // 首先比对传递过来的验证码是否和redis中存储的验证码相同
        checkCode(userDTO);
        // 通过上面的判断就说明输入的验证码是正确的，然后就判断该用户是注册还是登录
        User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
                .eq(User::getPhone,userDTO.getPhone()));
        // user为空则表示该用户为新用户
        if (user == null) {
            user = new User();
            user.setPhone(userDTO.getPhone());
            user.setStatus(UserStatus.Normal.getValue());
            user.setCreateBy(Constants.SYSTEM_USER_ID);
            userMapper.insert(user);
        }
        return tokenService.createToken(user.getUserId(), secret
                , UserIdentity.ORDINARY.getCode(), user.getNickName(), user.getHeadImage());
    }

    @Override
    public boolean logout(String token) {
        if (StrUtil.isNotEmpty(token) && token.startsWith(HttpConstants.PREFIX)) {
            token = token.replaceFirst(HttpConstants.PREFIX, StrUtil.EMPTY);
        }
        return tokenService.deleteLoginUser(token,secret);
    }

    @Override
    public R<LoginUserVO> info(String token) {
        // 对前端传来的token进行校验
        if (StrUtil.isNotEmpty(token) && token.startsWith(HttpConstants.PREFIX)) {
            token = token.replaceFirst(HttpConstants.PREFIX, StrUtil.EMPTY);
        }
        LoginUser loginUser = tokenService.getLoginUser(token, secret);
        if (loginUser == null) {
            return R.fail();
        }
        LoginUserVO loginUserVO = new LoginUserVO();
        loginUserVO.setNickName(loginUser.getNickName());
        if (StrUtil.isNotEmpty(loginUser.getHeadImage())) {
            loginUserVO.setHeadImage(downloadUrl + loginUser.getHeadImage());
        }
        return R.ok(loginUserVO);
    }

    @Override
    public UserVO detail() {
        Long userId = ThreadLocalUtil.get(Constants.USER_ID, Long.class);
        if (userId == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        // 先从缓存中查看是否存在该用户的缓存信息
        UserVO userVO = userCacheManager.getUserById(userId);
        if (userVO == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        if (StrUtil.isNotEmpty(userVO.getHeadImage())) {
            userVO.setHeadImage(downloadUrl + userVO.getHeadImage());
        }
        return userVO;
    }

    /**
     * 编辑成功的话不仅需要修改数据库中的数据，还需要修改redis缓存中一些基本信息的缓存以及详细信息的缓存
     * @param userUpdateDTO
     * @return
     */
    @Override
    public int edit(UserUpdateDTO userUpdateDTO) {
        return updateUserDetail(userUpdateDTO);
    }

    @Override
    public int updateHeadImage(UserUpdateDTO userUpdateDTO) {
        return updateUserDetail(userUpdateDTO);
    }

    private int updateUserDetail(UserUpdateDTO userUpdateDTO) {
        Long userId = ThreadLocalUtil.get(Constants.USER_ID, Long.class);
        if (userId == null) {
            throw new ServiceException(ResultCode.FAILED_NOT_EXISTS);
        }
        // 从数据库中查询该用户是否存在
        User user = userMapper.selectById(userId);
        if (user == null) {
            throw new ServiceException(ResultCode.FAILED_NOT_EXISTS);
        }
        // 对参数校验完成之后就修改缓存中的数据以及MySQL中的数据
        BeanUtil.copyProperties(userUpdateDTO,user);
        // 更新缓存
        // 更新详细信息的缓存
        userCacheManager.refreshUser(user);
        // 更新基本信息的缓存
        tokenService.refreshLoginUser(user.getNickName(), user.getHeadImage()
                ,ThreadLocalUtil.get(Constants.USER_KEY, String.class));
        // 更新MySQL数据库
        return userMapper.updateById(user);
    }

    private void checkCode(UserDTO userDTO) {
        String phoneCodeKey = getPhoneCodeKey(userDTO.getPhone());
        String code = redisService.getCacheObject(phoneCodeKey, String.class);
        // 如果code为空则表示验证码失效了
        if (code == null) {
            throw new ServiceException(ResultCode.FAILED_INVALID_CODE);
        }
        if (!code.equals(userDTO.getCode())) {
            throw new ServiceException(ResultCode.FAILED_ERROR_CODE);
        }
    }

    private void checkParams(UserDTO userDTO) {
        if (!checkPhone(userDTO.getPhone())) {
            throw new ServiceException(ResultCode.FAILED_USER_PHONE);
        }
        if (userDTO.getCode() == null || userDTO.getCode().isEmpty()) {
            throw new ServiceException(ResultCode.FAILED_ERROR_CODE);
        }
    }

    public boolean checkPhone(String phone) {
        Pattern regex = Pattern.compile("^1[2|3|4|5|6|7|8|9][0-9]\\d{8}$");
        Matcher m = regex.matcher(phone);
        return m.matches();
    }

    private String getPhoneCodeKey(String phone) {
        return CacheConstants.PHONE_CODE_KEY + phone;
    }

    private String getCodeTimeKey(String phone) {
        return CacheConstants.CODE_TIME_KEY + phone;
    }
}
